#|_________________________________________________________
 |
 |   newload.lsp - copyright (c) 2001-2 Forrest W. Young.
 |
 |
 |   For the features implemented in this file to work,
 |   .LSP, .VIS, .VISTA, .VAF and .VDF  files must be
 |   associated with LispBoss, not with LspEdit or ViSta
 | 
 |
 |   FIXES    problems with files whose pathnames have spaces.
 |            LSPEDIT.EXE can be used to edit files which are
 |            in directories whose paths contain spaces.
 |
 |
 |   ENHANCES file extensions recognized by system. The following
 |            Lisp Program files extensions are recognized:
 |            
 |____________________________________________________________
 |
 |   The following file extensions are recognized:
 |
 |   LSP - ViSta DataFile.
 |         A LSP file is a ViSta DataFile if it is located in a 
 |         directory structure with at least one level named 
 |         DATA, DATALIBRARY, MYDATA or MY-DATA (capitalization 
 |         ignored). A ViSta DataFile must contain a single ViSta
 |         Data function. The file is evaluated by ViSta in the
 |         same way a .VDF file is evaluated.
 |   
 |   LSP - LspEdit File
 |         A LSP file is a LspEdit file if there is no 
 |         DATA, DATALIBRARY, MYDATA or MY-DATA directory  
 |         in its directory structure. It can contain  
 |         any Lisp code. Processed by LspEdit
 |   
 |   VDF - ViSta DataFile. 
 |         Must contain a single ViSta data function for evaluting by ViSta.
 |         The data function must have the keyword VDF specified as T.
 |   
 |   VAF - ViSta Applet File. 
 |   VIS - ViSta Visualization File.
 |         These must contain Lisp programs appropriate for evaluting by ViSta.
 |         There is no difference in the way these two filetypes are processed.
 |   
 |   FSL - XLispStat bytecode file. 
 |   WKS - XLispStat WorkSpace file.
 |         These file types continue to be processed by ViSta as before.
 |   
 |   If there is no extension specified, it is assumed that it should 
 |   be either .LSP or .FSL, so LispBoss looks for the most recent 
 |   of these two. and then uses ViSta to load it.
 |
 |   A more sophisticated way to define a LSP ViSta Data File than 
 |   on the basis of its directory name is needed.
 |_________________________________________________________________________
 |

LspEdit cannot edit files which reside in a directory whose pathname contains blanks (such as files in the PROGRAM FILES directory structure!) The bug is that LspEdit cannot process pathnames which are inside quotes. However, DOS automatically surrounds such pathnames which quotes, resulting in LspEdit being unable to process such files.

The bug is not actually fixed. Rather, the system is hacked to circumvent the problem. The hack involves runing XLispStat rather than LspEdit whenever you double-click or otherwise run a .LSP file.  Then, XLispStat's PATH functions are used to process the paths, and the SYSTEM function is used to invoke the editor. Specifically:
  1 XLisp is used to change the working directory to the directory containing the file to be edited.
  2 The (system) function invokes the editor without the pathname. The pathname is not needed because the working directory has been changed. Thus, the bug is avoided.
  3 XLisp is then exited.

XLispStat avoides the problem of filenames with spaces in them by always placing the filename inside quotes. Unfortunately, this introduces another bug, since we will now sometimes have a filename inside two pairs of double quotes. Thus, the first step of the code is to get rid of the extra quotes when present.
|#

(in-package "XLISP")
(setf *load-verbose* t)
(setf *startup-path* user::*startup-path*)
(setf *edit-lisp-files* nil)
(setf *ini-file* user::*ini-file*)

(format t "; extended load function~%")

(defconstant dblquote (code-char 34))
(setf *edit-lisp-files-choice* t)
(setf *if-does-not-exist* t)
(setf *home* *default-path*)
(defun strcat (&rest args) (apply #'concatenate 'string args))

(defun pathname-directoryname (file)
  (namestring-directory file))

(defun namestring-directory (file)
  (let ((dir-struct (rest (pathname-directory file)))	
        (pathname ""))
     (first (last (mapcar #'(lambda (dir)
                (setf pathname (format nil "~a\\~a" pathname dir)))
              dir-struct)))))

(defun pathless-file-namestring (file-namestring)
  (reverse (subseq (reverse file-namestring) 
                   0 (position #\\ (reverse file-namestring)))))

(defun pathname-file (filename)
  (strcat (pathname-name filename) "." (pathname-type filename)))

(setf knt 0)
(setf knt-auto 0)
(setf knt-init 0)
(setf knt-statinit 0)
(setf *bad-file* nil)
(setf *sys-file* nil)
(setf *frag-file* nil)

(setf split-file t)
(setf prev-file " ")
                    

(defun find-spaces (str)
  (let* ((i 0) (j 0) (k 0) (locs nil) (L (length str)))
    (loop
     (setf i (position #\space (select str (iseq j (1- L)))))
     (cond
      (i
       (setf j (+ I J 1))
       (setf k (1+ k))
       (setf locs (append locs (list j)))
       )
     (t (return))))
     locs))
                                               
(defun load (file &key (verbose *load-verbose*) 
                  (print *load-print*) 
                  (if-does-not-exist t)
                  (edit *edit-lisp-files*)
                  (dialog *edit-lisp-files-choice*)) 
"Args: (file &key (verbose *load-verbose*) 
                  (print *load-print*) 
                  (if-does-not-exist t)
                  (edit *edit-lisp-files*)) 

Extends capabilities of the XLispStat load function. 

NEW CAPABILITIES: 
(1) Provides choice of editing or loading a .LSP file
    
 a) By default a .LSP file is processed as in XLISPSTAT 
    unless the file is a ViSta datafile. Currently, 
    a ViSta datafile is a .LSP file residing in a directory
    structure containing a subdirectory named DATA, DATALIBRARY, 
    MYDATA or MY-DATA (capitalization ignored).
 b) If *edit-lisp-files* is NIL and the file is not a datafile 
    a .LSP file is sent by LISPBOSS to LSPEDIT for editing
 c) Regardless of the value of *EDIT-LISP-FILE*, if the
    .LSP file is a datafile it is sent by LISPBOSS to ViSta

(2) Runs ViSta     for file types .vis, .vaf and .vdf
    Runs XLispStat for file types .lsp, .fsl and .wks.

BUG FIX: When the request to edit a .lsp file is preprocessed by this XLispStat load function, LspEdit can edit files in directories whose name contain spaces.
Forrest Young, Nov, 2001"
#|
  The main motivation for this extensive and complicated hack is the inability of LspEdit to edit files which reside in a directory whose pathname contains one or more spaces. LspEdit cannot access such files because it incorrectly processes paths which are quoted. When it processes such a path, it sees two file names, the names being the strings before and after the space.
  More specifically, whenever DOS sees a filename with a blank, it generates a filename which is a quoted string. However, XLisp then turns it into a quoted quoted string, and then proceeds incorrectly to process it as  two quoted strings, the first with an extra leading quote, the second with an extra trailing quote. 
  Note that the bug is not actually fixed, but that XLispStat is used to preprocess the edit request so that the bug is avoided. The bug is detected by noticing whether what is supposed to be a file name contains an extra leading quote. If so, the next string is read to see if it contains an extra trailing quote, in which case the strings are concatenated with the resulting string being used as the file name.
  Finally, in the process of fixing this, a bug was found in XLispStat concerning the processing of the double-quote character. 
|#

  (setf knt (1+ knt))
  (format t "; initial  ~a (~d)~%" file knt)

  (when (> knt 3) ;(= knt 4)
        (setf file (strcat prev-file " " file))
        (format t "; joined  ~a~%" file)
        )
  (setf prev-file file)
  (if (equal (select file 1) #\:)
      (progn
       (format t "; look at  ~a~%" file)
       (setf file file)
       )
      (when (equal (select file 2) #\:)
            (cond  
              ((and (equal (select file 0) dblquote)
                    (equal (select (reverse file) 0) dblquote))
               (format t "; dequote ~a~%" file)
               (setf file (select file (iseq 1 (- (length file) 2))))
               (setf split-file nil)
               )
              ((equal (select file 0) dblquote)
               (setf file (select file (iseq 0 (- (length file) 1))))
               (format t "; joining~%")
               (setf split-file t)
               (setf prev-file file)
               ))))       
  (cond 
    ((equal "autoload" (string-downcase (first (last (pathname-directory file)))))
     (format t "; skipped  ~a~%" file))
    ((and (< knt 3) (equal (pathname-name file) "_autoidx"))
     (format t "; skipped  ~a~%" file))
    ((and (< knt 3) (equal (pathname-name file) "statinit"))
     (format t "; skipped  ~a~%" file))
    ((and (< knt 3) (equal (pathname-name file) "init"))
     (format t "; skipped  ~a~%" file))
    ((not (not (position #\. file)))
     (format t "~%; FOUND    ~a~%" file)
     (do-loadboss file verbose print if-does-not-exist))
    (split-file)
    (t 
     (format t "~%; FOUND    ~a~%" file)
     (do-loadboss file verbose print if-does-not-exist))))

;;;;
;;;; loadboss
;;;;





(defun do-loadboss (file &rest args)
  "Args: (file)
Load or edit FILE, using the extension of the file to determine what is done with the file. Acceptable file extensions, and what happens with them, are as follows:
      LSP - LISP FILE. This file type must contain Lisp code appropriate for editing or evaluating. Whether the file is loaded by ViSta or edited by LspEdit depends on the value of the environmental variable *edit-lisp-files* and on whether the file is a datafile.
            DATAFILES are always loaded, regardless of *edit-lisp-files*. Currently, a file is defined to be a datafile if it is in a directory whose path contains a directory named DATA, DATALIBRARY, MYDATA or MY-DATA (capitalization ignored).
            PROGFILES (i.e., not datafiles) are loaded by ViSta or edited by LspEdit depending on the value of *edit-lisp-files* 
     VDF - VISTA DATA FILE. This file type uses ViSta to load and run ViSta datafiles. This file type must contain ViSta DataCode programs appropriate for evaluting by ViSta. It is assumed that ViSta resides on a client computer. The file can reside on the local client computer or can be served to the local client computer by a remote server.
     VAF - VISTA APPLET FILE. This file type must contain Lisp programs appropriate for evaluting by ViSta. It is assumed that ViSta resides on a client computer. The file can reside on the local c;ient computer or can be served to the local client computer by a remote server. Treated identically to .VIS files.
     VIS - VISTA PROGRAM FILE. This file type must contain Lisp programs appropriate for evaluting by ViSta. It is assumed that ViSta resides on a client computer. The file can reside on the local client computer or can be served to the local client computer by a remote server. Treated identically to .VAF files.
     FSL - XLispStat Byte-Code file. This file type uses ViSta to load compiled bytecode files. The compiled bytecode can result from compiling any of the file types above.
     WKS - XLispStat WorkSpace file. This file type uses ViSta to load an XLispStat workspace.
     NO EXTENSION - If there is no extension specified, it is assumed that it should be either .LSP or .FSL, and looks for the most recent of these two."
  (let* ((type (string-downcase (pathname-type file)))
         (xlisp-load-types (list "fsl" "wks" "nil"))
         (vista-load-types (list "vis" "vdf" "vaf"))
         (edit-types (list "lsp"))
         (do-what? (string-downcase
                    (msw-get-profile-string  
                     "XLisp" "LoadLisp?" *ini-file*)))
         )

    (setf *datafile* (or (equal type "vdf") (datafile? file)))
    
    (setf *edit-lisp-files* 
          (cond 
            (*datafile* 
             (when (equal type "lsp")(format t "; loading .lsp data~%"))
             nil)
            ((equal "yes" do-what?)
             (when (equal type "lsp")(format t "; loading .lsp code ~%"))
             nil)
            ((equal "no" do-what?)
             (when (equal type "lsp")(format t "; editing .lsp code ~%"))
             t)
            ((equal "ask" do-what?)
             (when (equal type "lsp")(format t "; asking  .lsp code ~%"))
             (edit-run-dialog))
            (t (error "; corrupted ini-file loadlisp information"))))
    
    
    (format t "; routing  ~a to " file)
    (cond 
      ((and (equal type "lsp"))
       (format t "~a~%" (if *edit-lisp-files* "an editor" "vista"))
       (if *edit-lisp-files*
           (do-process file :edit t)
           (do-process file :load t) ;file process unspecified. must be edit or load
           ))

      ((not (not (member type edit-types :test #'equal)))
       (format t "~a~%" "lspedit")
       (apply #'do-edit file args))

      ((not (not (member type xlisp-load-types :test #'equal)))
       (format t "~a~%" "xlisp")
       (xlisp::do-load file *load-verbose* *load-print*))

      ((not (not (member type vista-load-types :test #'equal)))
       (format t "~a~%" "vista")
       (do-vista-load file))

      ((equal type "nil")
       (format nil "; type nil~%")
       (let* ((lspfile (merge-pathnames file ".lsp"))
	      (fslfile (merge-pathnames file ".fsl"))
              (lsp-exists (probe-file lspfile))
              (fsl-exists (probe-file fslfile)))
         (cond 
           ((or (and fsl-exists
                     lsp-exists
                     (< (file-write-date lspfile) 
                        (file-write-date fslfile)))
                (format nil "; lsp-fsl  fsl oldest~%")
                (and fsl-exists (not lsp-exists)))
            (apply #'do-eval fslfile args))
           (lsp-exists
            (format nil "; lsp-fsl  lsp only~%")
            (xlisp::do-load lspfile *load-verbose* *load-print* ))
           (t
            (maxmainwindow)
            (format nil "; lsp-fsl  elsewise~%")
            (error "Unspecified File Type - assumed to be .lsp or .fsl - not found.")))))
      (t
       (error "Unrecognized File Type."))))
  )


(defun operational? ()
  (equal "yes"
         (string-downcase
          (msw-get-profile-string "ViSta" "ViStaOp" USER::*HOME-ini-file*))))

(defun running? ()
  (equal "yes"
         (string-downcase
          (msw-get-profile-string "ViSta" "ViStaRunning" USER::*HOME-ini-file*))))

(defun infile? ()
  (not (equal "no"
         (string-downcase
          (msw-get-profile-string "ViSta" "InFile" USER::*HOME-ini-file*)))))


(defun datafile? (file)
  (let* ((directories (map-elements 
                       #'string-downcase  
                       (pathname-directory file)))
         )
    (or
     (not (not (member "data" directories :test #'equal)))
     (not (not (member "datalibrary" directories :test #'equal)))
     (not (not (member "mydata" directories :test #'equal)))
     (not (not (member "my-data" directories :test #'equal))))))

(defun edit-run-dialog ()
  (apply #'mainwindow 0 0  (+ 10 10 (screen-size)))
  (choice-item-dialog 
   (strcat "When Opening a .LSP file:") 
   (list "EDIT with LspEdit" "RUN with ViSta" "EXIT FileBoss")
   (list '(setf *edit-lisp-files* t) '(setf *edit-lisp-files* nil) '(exit))
   :title "ViSta FileBoss"
   :initial (if *edit-lisp-files* 0 1)
   :location '(30 30)))
    

(defun do-edit (file &rest args)
  (do-process file :edit t))

(defun do-vista-load (file &rest args)
  (do-process file :load t))


(defun do-process (file &rest args &key edit load) ;function added by fwy
"Args: (file)
When EDIT is T, opens an editing window displaying FILE ready for editing byLspEdit. When LOAD is T, when no other XLisp is running it runs XLisp and then causes XLisp to load FILE (optionally does not run XLisp if it is already running, just loading the FILE)."
  (format t "; process  ~a~%" file)
  (when (and (not edit) (not load))
        (error "file process unspecified. must be edit or load"))
  (let* ((in-dir (get-working-directory))
       	 (home-dir (set-working-directory (strcat *startup-path* "..")))
       	 (home-dir (get-working-directory))
         (in-dir (set-working-directory in-dir))
         (in-dir (get-working-directory))
       	 (dir (pathname-directoryname file))
         (short-filename (pathname-file file))
         (device (pathname-device file))
         (lspedit.exe (strcat home-dir "\\lspedit.exe"))
         (vista.exe   (strcat home-dir "\\vista.exe"))
         (notepad.exe "notepad.exe")
         (lspedit.exe-a-file (strcat lspedit.exe " " short-filename))
         (notepad.exe-a-file (strcat notepad.exe " " short-filename))
         (vista.exe-a-file   (strcat vista.exe  " -f  "short-filename))
         (vista.exe-a-pathed-file (strcat vista.exe  " -f  " file))
         (fl))
    (cond ;EDIT FILE
      (edit
       (set-working-directory in-dir)
       (setf fl (user::file-length (user::open file)))	
       (when (> fl 29000)
             (format t "; editing  ~a with NotePad~%" file)
             (user::message-dialog "Long File Edited with NotePad")
             (system notepad.exe-a-file)
             (see-if-exit))
       (when (< fl 32001)
             (format t "; editing  ~a with LispEdit~%" file)
             (system lspedit.exe-a-file)
             (see-if-exit)))
      (t ;LOAD FILE
       (let* ((vista-operational? (operational?))
              (vista-running? (running?))
              (infile (msw-get-profile-string 
                "ViSta" "InFile" USER::*HOME-ini-file*))
              (infile? (infile?))
              (verbose nil)
              (corrupt))
         (when (or (and vista-operational? vista-running? infile?)
                   (not (equal vista-operational? vista-running?)))
               (setf vista-running? nil)
               (setf vista-operational? nil)
               (msw-write-profile-string 
                 "ViSta" "ViStaOp"      "No" USER::*home-ini-file*)
               (msw-write-profile-string 
                 "ViSta" "ViStaRunning" "No" USER::*home-ini-file*)
               (setf corrupt t))
         #|
         (when (and vista-operational? vista-running? infile?)
               (message-dialog (format nil "Corrupted INI File.~2%Processing previous file~%with new instance of ViSta."))
               (setf vista-running? nil)
               (setf vista-operational? nil)
               (setf file infile)
               (msw-write-profile-string 
                 "ViSta" "ViStaOp"      "No" USER::*home-ini-file*)
               (msw-write-profile-string 
                 "ViSta" "ViStaRunning" "No" USER::*home-ini-file*)
               )
         (when (not (equal vista-operational? vista-running?))
               (setf vista-running? vista-operational?)
               (if infile? (setf file infile))
               (message-dialog (format nil "Corrupted INI File.~2%Assuming ViSta not Running.~%Processing ~a file~%with new instance of ViSta." "requested" "previous"))
               (msw-write-profile-string 
                 "ViSta" "ViStaOp"      "No" USER::*home-ini-file*)
               (msw-write-profile-string 
                 "ViSta" "ViStaRunning" "No" USER::*home-ini-file*)
               )
         |#
         (set-working-directory home-dir)
         (msw-write-profile-string "ViSta" "InFile" file USER::*home-ini-file*)
         (if verbose
             (message-dialog 
              (format nil "~a INI File.~2%Loading Requested File~%~a~%Using ~a instance of ViSta" 
                      (if corrupt "Corrupt" "Intact")
                      file 
                      (if vista-running? "running" "new"))))
         (format t "; loading  ~a into ~a instance of ViSta~%" file 
                 (if vista-running? "running" "new"))
         (unless vista-operational?
                (system vista.exe)
                (loop
                 (when (equal 
                        (string-downcase
                         (msw-get-profile-string "ViSta" "ViStaOp" 
                                                 USER::*home-ini-file*))
                        "yes")
                       (see-if-exit file))))
         (see-if-exit))))))
 
 
(defun see-if-exit (&optional file)
  (msw-write-profile-string "ViSta" "LispBossRunning" "No" USER::*ini-file*)
  (cond
    ((equal (string-downcase
              (msw-get-profile-string "ViSta" "Exit" USER::*ini-file*))
             "no")
     (user::top-level) 
     )
    (t
    ; (when file 
          ; (message-dialog "LispBoss writing file now that ViSta is Up")
          ; (msw-write-profile-string "ViSta" "InFile" file USER::*home-ini-file*))
     (exit))))

(defun choice-item-dialog (text item-string-list action-list 
                               &key (show t) (title "Choose Items") (initial nil)
                              (size nil) (location nil) (close nil))
"Args: TEXT ITEM-STRING-LIST ACTION-LIST &Key SHOW TITLE SIZE LOCATION INITIAL CLOSE
Presents a modeless choice item dialog box. TEXT appears in the dialog box before the choice items, unless NIL. ITEM-STRING-LIST is a list of choice-item strings. ACTION-LIST is a list of action functions, one for each choice. INITIAL is a number specifying the initial choice (nil - none checked - by default). A Close Item is included by default."
  (let* ((text (if text (send text-item-proto :new text)) nil)
         (choice (send choice-item-proto  :new (append item-string-list (list " "))
                       :value (if initial initial (length item-string-list))
                       :action #'(lambda () (send self :do-choice-action))))
         (cancel (send toggle-item-proto :new "Cancel" 
                      :action #'(lambda () (send self :close))))
         (dialog (send dialog-proto :new 
                       (if text (list text choice cancel) (list choice))
                       :show show :title title
                       :size size :location location)) )
    (defmeth choice :do-choice-action ()
      (eval (select action-list (send choice :value))))
   ; (defmeth choice :cancel () 
   ;   (send dialog :close))
    (if (send dialog :size)
        (send dialog :size (first (floor (* 4/5 (send dialog :size))))
              (second (send dialog :size)))
        (send dialog :show-window))
    (apply #'send dialog :size (- (send dialog :size) '(0 24)))
    dialog))

                                           
